package zdream.pmfield.test.stage.effect;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;

import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.data.foe.FoeTemplate;
import zdream.pmfield.data.foe.MoveTemplate;
import zdream.pmfield.data.foe.MoveType;
import zdream.pmfield.data.foe.Type;
import zdream.pmfield.stage.StageBuilderForTest;
import zdream.pmfield.stage.StageWrapper;
import zdream.pmfield.stage.action.AttackAction;
import zdream.pmfield.stage.action.AttackReport;
import zdream.pmfield.stage.director.FixPair;
import zdream.pmfield.stage.message.ActionStartMessage;
import zdream.pmfield.stage.message.AttackMessage;
import zdream.pmfield.stage.message.RollReportMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.storage.FoeTemplateStorage;
import zdream.pmfield.test.utils.ResourceConstant;
import zdream.pmfield.test.utils.StageConstant;

import static org.junit.Assert.*;

/**
 * @author Zdream
 * @date 2023-11-17
 * @since 0.0.1
 */
public class TypeFixRateCase {
	FoeTemplateStorage storage;

	@Before
	public void before() {
		storage = new FoeTemplateStorage();
		storage.load();
		storage.loadMovesInResource(ResourceConstant.RES_PATH_TEST_MOVES_JSON);
	}

	@Test
	public void testFixRate() {
		FixPair pair = FixPair.of(Type.FIRE, Type.GRASS, 1.0f);
		float rate1 = FixPair.fixedRateTotal(Collections.singletonList(pair));
		System.out.println("rate1=" + rate1);
		assertTrue(rate1 > 1);

		pair = FixPair.of(Type.GRASS, Type.FIRE, 1.0f);
		float rate2 = FixPair.fixedRateTotal(Collections.singletonList(pair));
		System.out.println("rate2=" + rate2);
		assertTrue(rate2 < 1);
		assertTrue(rate2 > 0);

		pair = FixPair.of(Type.NORMAL, Type.FIRE, 1.0f);
		float rate3 = FixPair.fixedRateTotal(Collections.singletonList(pair));
		System.out.println("rate3=" + rate3);
		assertEquals(rate3, 1f, 0f);

		FixPair pair1 = FixPair.of(Type.GRASS, Type.FIRE, 1.0f);
		FixPair pair2 = FixPair.of(Type.GRASS, Type.GRASS, 1.0f);
		float rate4 = FixPair.fixedRateTotal(Arrays.asList(pair1, pair2));
		System.out.println("rate4=" + rate4);
		assertTrue(rate4 < 1);
		assertTrue(rate4 < rate2);
		assertTrue(rate4 > 0);

		pair1 = FixPair.of(Type.FIRE, Type.GRASS, 1.0f);
		pair2 = FixPair.of(Type.FIRE, Type.STEEL, 1.0f);
		float rate5 = FixPair.fixedRateTotal(Arrays.asList(pair1, pair2));
		System.out.println("rate5=" + rate5);
		assertTrue(rate5 > 1);
		assertTrue(rate5 > rate1);

		pair1 = FixPair.of(Type.FIRE, Type.GRASS, 1.0f);
		pair2 = FixPair.of(Type.FIRE, Type.WATER, 1.0f);
		float rate6 = FixPair.fixedRateTotal(Arrays.asList(pair1, pair2));
		System.out.println("rate6=" + rate6);
		assertEquals(rate6, 1f, 0f);

		pair1 = FixPair.of(Type.ELECTRIC, Type.SAND, 1.0f);
		float rate7 = FixPair.fixedRateTotal(Arrays.asList(pair1));
		System.out.println("rate7=" + rate7);
		assertTrue(rate7 == 0);
	}

	@Test
	public void testFixRateInBattle() {
		MoveTemplate moveGrass = storage.findMoveById(22, 1);
		assertEquals(moveGrass.mainType(), Type.GRASS);

		final FoeTemplate foeTemplate4 = storage.findFoeById(4);
		assertArrayEquals(foeTemplate4.types, new Type[] {Type.FIRE});

		final FoeTemplate foeTemplate1 = storage.findFoeById(1);
		assertArrayEquals(foeTemplate1.types, new Type[] {Type.GRASS});

		Foe foe1 = Foe.builder(foeTemplate4)
				.withLevel(20)
				.withMove(storage.findMoveById(1, 1))
				.build();
		Foe foe2 = Foe.builder(foeTemplate1)
				.withLevel(20)
				.withMove(moveGrass)
				.build();
		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();
		AtomicBoolean checked = new AtomicBoolean(false);

		wrapper.onDefaultActionRequest();
		wrapper.printBeforeHandleMessage();
		wrapper.onMessage(AttackMessage.class, message -> {
			final AttackReport report = message.report;
			if (!(report.action instanceof AttackAction)) {
				return;
			}

			final AttackAction attackAction = (AttackAction) report.action;
			if (attackAction.foe.foe != foe2) {
				return;
			}

			assertFalse(report.items.isEmpty());
			assertEquals(report.items.size(), 1);

			final AttackReport.AttackReportItem item = message.report.items.get(0);
			assertTrue(item.typeFixed < 1); // 草系打火系
			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
	}

	@Test
	public void testFixedRateEffect() {
		final FoeTemplate foeTemplate4 = storage.findFoeById(4);

		final FoeTemplate foeTemplate76 = storage.findFoeById(76);
		assertTrue(Arrays.asList(foeTemplate76.types).contains(Type.STEEL));

		MoveTemplate fixedMove = storage.findMoveById(2, 3);
		assertEquals(fixedMove.name, "空手劈");

		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals(normalMove.name, "无效果");

		Foe foe1 = Foe.builder(foeTemplate4)
				.withLevel(20)
				.withMove(fixedMove)
				.build();
		Foe foe2 = Foe.builder(foeTemplate76)
				.withLevel(20)
				.withMove(normalMove)
				.build();
		StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();

		AtomicBoolean checked = new AtomicBoolean(false);
		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.onMessage(AttackMessage.class, message -> {
			final AttackReport report = message.report;
			final AttackAction action = report.action.asAttack();
			if (action.foe.foe == foe2) {
				return;
			}
			assertEquals(report.items.size(), 1);
			final AttackReport.AttackReportItem item = report.items.get(0);
			assertEquals(item.foe.foe, foe2);
			if (item.damage == 0) {
				// 可能大失败了
				return;
			}
			assertTrue(item.typeFixed > 1);
			checked.set(true);
		});
		wrapper.runUntil(stage -> checked.get());
	}

	/**
	 * 本系加成对 Movement 类型技能无效
	 */
	@Test
	public void testSameTypeBonusForMovementMoves() {
		MoveTemplate movementMove = storage.findMoveById(10000018, 1);
		assertEquals(movementMove.name, "行动类");
		assertEquals(movementMove.attackerUsing, MoveType.MOVEMENT);
		final int rollCount = movementMove.vStats.length;

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(movementMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(25))
				.withLevel(30)
				.withMove(movementMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();

		assertNotNull(wrapper.stage.findFoe(foe1).findMoveToMove());
		assertNotNull(wrapper.stage.findFoe(foe2).findMoveToMove());

		wrapper.printBeforeHandleMessage();
		wrapper.onActionRequestForAll(wrapper.createDefaultController().withMove(movementMove));
		wrapper.runUntil(ActionStartMessage.class, message -> {
			assertEquals(movementMove, message.action.asAttack().move.move);
			return true;
		});
		wrapper.runUntil(RollReportMessage.class, message -> {
			if (!StageConstant.ROLL_TITLE_ACTION.equals(message.title)) {
				return false;
			}
			assertEquals(message.rolls.length, rollCount);
			return true;
		});
	}

	/**
	 * 本系加成对 0 骰子技能无效
	 */
	@Test
	public void testSameTypeBonusFor0VStatMoves() {
		MoveTemplate move = storage.findMoveById(10000019, 1);
		assertEquals(move.name, "零骰子");
		assertEquals(move.vStats.length, 0);
		assertEquals(move.attackerUsing, MoveType.PHYSICAL);
		assertEquals(move.mainType(), Type.FIRE);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(move)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(25))
				.withLevel(30)
				.withMove(move)
				.build();
		assertEquals(foe1.template.types[0], Type.FIRE);

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();

		AtomicBoolean checked = new AtomicBoolean(false);
		AtomicBoolean moveRequest = new AtomicBoolean(false);

		wrapper.printBeforeHandleMessage();
		wrapper.onActionRequestForAll(wrapper.createDefaultController().withActionChecker(action -> {
			if (action.move.move == move && action.foe.foe == foe1) {
				moveRequest.set(true);
			}
		}));
		wrapper.onMessage(RollReportMessage.class, message -> {
			if (!StageConstant.ROLL_TITLE_ACTION.equals(message.title)) {
				return;
			}
			if (!moveRequest.get() && message.foe.foe != foe1) {
				return;
			}
			throw new RuntimeException("使用 0 骰子的技能时, 不应该发送 RollReportMessage");
		});
		wrapper.onMessage(AttackMessage.class, message -> {
			final AttackReport report = message.report;
			final FoeActor foe = report.action.asAttack().foe;
			if (foe.foe == foe1) {
				checked.set(true);
			}
		});

		wrapper.runUntil(stage -> checked.get());
		assertTrue(checked.get());
	}
}
